/*-
 * Copyright (C) 2018-2020, Shanghai Trusted Industry Control Platform Co., Ltd.
 * Created by 周正辉 (Zhenghui Zhou) <zhouzhenghui@ticpsh.com>, 赵楷文 (Kaiwen
 * Zhao) <zhaokaiwen@ticpsh.com>
 */

#include "libtestud.h"

#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Tooling/Tooling.h>

#include <map>
#include <memory>

#include "testud.h"

using namespace llvm;
using namespace clang;
using namespace clang::tooling;
using njson = nlohmann::json;

namespace rocket {
namespace testif {

// 报文检索与过滤
class TestUDTFrontHelpActionFactory
    : public clang::tooling::FrontendActionFactory {
 public:
  njson &parsedifs_j;
  njson &udspecs_j;
  std::vector<std::string> &udspecids;

  TestUDTConsumer *udt_consumer = nullptr;
  std::map<std::string, std::string> relative_paths;

  const share::RocketAnalyzeSetting &setting;

 public:
  TestUDTFrontHelpActionFactory(const std::vector<std::string> &files,
                                njson &parsedifs_j, njson &specs_j,
                                std::vector<std::string> &udspecids,
                                const share::RocketAnalyzeSetting &setting)
      : parsedifs_j(parsedifs_j),
        udspecs_j(specs_j),
        udspecids(udspecids),
        setting(setting) {
    for (auto &file : files) {
      relative_paths[boost::filesystem::absolute(file).string()] = file;
    }
  }

 public:
  class TestUDTFrontendAction : public ASTFrontendAction {
   public:
    TestUDTFrontendAction(TestUDTFrontHelpActionFactory *action_factory_)
        : action_factory(action_factory_) {}
    std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &,
                                                   StringRef file) override {
      njson::iterator parsed_if_it = action_factory->parsedifs_j.begin();
      njson::iterator parsed_if_it_end = action_factory->parsedifs_j.end();

      for (; parsed_if_it != parsed_if_it_end; ++parsed_if_it) {
        std::string file = (*parsed_if_it)["file"].get<std::string>();
        if ((*parsed_if_it)["file"] ==
            action_factory->relative_paths[(std::string)file]) {
          break;
        }
      }

      if (parsed_if_it == parsed_if_it_end)
        throw std::runtime_error("could not find file:" + (std::string)file);

      auto diag_it = parsed_if_it->find("diag");
      if (diag_it == parsed_if_it->end()) {
        (*parsed_if_it)["diag"] = njson::array();
        diag_it = parsed_if_it->find("diag");
      }

      auto types_it = parsed_if_it->find("types");
      auto literals_it = parsed_if_it->find("literals");
      auto globals_it = parsed_if_it->find("globals");
      auto functions_it = parsed_if_it->find("functions");
      //      auto functiondecls_it = parsed_if_it->find("functiondecls");
      auto macros_it = parsed_if_it->find("macros");
      auto code_line_it = parsed_if_it->find("lineStatistic");

      auto fixed_address_it = parsed_if_it->find("fixedAddrs");
      if (fixed_address_it == parsed_if_it->end()) {
        (*parsed_if_it)["fixedAddrs"] = njson::array();
        fixed_address_it = parsed_if_it->find("fixedAddrs");
      }

      //      {
      //        auto functiondecls_it = parsed_if_it->find("functiondecls");
      //        if (functiondecls_it == parsed_if_it->end()) {
      //          (*parsed_if_it)["functiondecls"] = njson::object();
      //          functiondecls_it = parsed_if_it->find("functiondecls");
      //        }
      //      }

      {
        if (macros_it == parsed_if_it->end()) {
          (*parsed_if_it)["macros"] = njson::object();
          macros_it = parsed_if_it->find("macros");
        }
      }

      {
        if (code_line_it == parsed_if_it->end()) {
          (*parsed_if_it)["lineStatistic"] = njson::object();
          code_line_it = parsed_if_it->find("lineStatistic");
        }
      }

      action_factory->udt_consumer = new TestUDTConsumer(
          action_factory->setting, *diag_it, *types_it, *literals_it,
          *globals_it, *functions_it, *fixed_address_it, /* *functiondecls_it,*/
          *macros_it, *code_line_it, action_factory->udspecs_j,
          action_factory->udspecids);

      return nullptr;
    }

   private:
    TestUDTFrontHelpActionFactory *action_factory;
  };

#if LLVM_VERSION_MAJOR < 10
  FrontendAction *create() override { return new TestUDTFrontendAction(this); }
#else
  std::unique_ptr<FrontendAction> create() override {
    return std::make_unique<TestUDTFrontendAction>(this);
  }
#endif
};

class TestUDTFrontendActionFactory {
 public:
  ASTConsumer *udt_consumer;

  std::unique_ptr<clang::ASTConsumer> newASTConsumer() {
    return std::unique_ptr<ASTConsumer>(udt_consumer);
  }
  TestUDTFrontendActionFactory(TestUDTConsumer *udt_consumer_)
      : udt_consumer(udt_consumer_) {}
};

void parse_udtype_interfaces(const CompilationDatabase &compilations,
                             const std::vector<std::string> &files,
                             nlohmann::json &parsed_ifs,
                             nlohmann::json &udspecs,
                             std::vector<std::string> &udspecids,
                             const share::RocketAnalyzeSetting &setting) {
  ClangTool tool(compilations, files);
#ifdef CLANG_RUNTIME_DIR
  tool.appendArgumentsAdjuster(
      tooling::getInsertArgumentAdjuster("-I" CLANG_RUNTIME_DIR "/include"));
#endif
  TestUDTFrontHelpActionFactory act_help_factory(files, parsed_ifs, udspecs,
                                                 udspecids, setting);
  tool.run(&act_help_factory);
  TestUDTFrontendActionFactory act_factory(act_help_factory.udt_consumer);
  tool.run(newFrontendActionFactory(&act_factory, act_help_factory.udt_consumer)
               .get());
}

}  // namespace testif
}  // namespace rocket
